package phase

import (
	"context"

	"code.byted.org/bytertc/whiteboard_server/biz/api/pb"
	v1 "code.byted.org/bytertc/whiteboard_server/biz/api/pb/v1"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/biz_error"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action"
	"code.byted.org/bytertc/whiteboard_server/biz/service/object"
	"code.byted.org/gopkg/logs/v2/log"
	"github.com/pkg/errors"
	"google.golang.org/protobuf/proto"
)

type Phase interface {
	GetAppId() string
	GetRoomId() string
	GetBoardId() int32
	GetPageId() string
	GetOperator() string
	GetObjId() string
	GetActionId() int32
	GetTimestamp() uint64
	GetPhase() v1.PhaseType
	Execute(input object.Element) object.Element
	ToProto() (*v1.Action, error)
	GetType() v1.ActionType
	IsCreatingObject() bool
	IsCreateActionId() bool
	IsModifyOnGoing() bool
	IsCompleteDrawAction() bool
}

func NewDrawPhase(ctx context.Context, info action.Info, rawAction *v1.Action) (p Phase, err error) {
	msg, err := pb.UnmarshalAction(rawAction.Type, rawAction.Data)
	if err != nil {
		return nil, biz_error.DecodeError.Wrap(errors.Wrapf(err, "fail to unmarshal %s", rawAction.Type))
	}
	switch rawAction.Type {
	case v1.ActionType_kPen:
		p = &drawPen{
			drawPhase: drawPhase{info, rawAction},
			Pen:       msg.(*v1.Pen),
		}
	case v1.ActionType_kLine, v1.ActionType_kArrow, v1.ActionType_kRect,
		v1.ActionType_kCircle, v1.ActionType_kText, v1.ActionType_kImage,
		v1.ActionType_kAudio, v1.ActionType_kVideo:
		p = &drawDefault{
			drawPhase: drawPhase{info, rawAction},
			Message:   msg,
		}
	default:
		return nil, biz_error.DecodeError.Wrap(errors.Errorf("no such drawing action %d", rawAction.Type))
	}
	log.V1.CtxInfo(ctx, "create action %s, %s, %d", rawAction.Type, rawAction.Phase, rawAction.Timestamp)
	return p, nil
}

func NewDrawPhases(ctx context.Context, appId, roomId string, packet *v1.Packet) ([]Phase, error) {
	info := action.Info{
		Operator: packet.Operator,
		AppId:    appId,
		RoomId:   roomId,
		BoardId:  packet.BoardId,
		PageId:   packet.PageId,
	}

	phases := make([]Phase, len(packet.Actions))
	for index := range packet.Actions {
		p, err := NewDrawPhase(ctx, info, packet.Actions[index])
		if err != nil {
			return nil, err
		}
		phases[index] = p
	}
	return phases, nil
}

type drawPhase struct {
	action.Info
	*v1.Action
}

func (b *drawPhase) emptyAction() *v1.Action {
	return &v1.Action{
		Type:      b.Type,
		Phase:     b.Phase,
		ObjId:     b.ObjId,
		ActionId:  b.ActionId,
		Timestamp: b.Timestamp,
	}
}

func (b *drawPhase) IsCreatingObject() bool {
	return b.GetActionId() == action.InitialActionId && (b.GetPhase() == v1.PhaseType_kStart || b.GetPhase() == v1.PhaseType_kFull)
}

func (b *drawPhase) IsCreateActionId() bool {
	return b.GetActionId() == action.InitialActionId
}

// IsModifyOnGoing 二次编辑OnGoing动作
func (b *drawPhase) IsModifyOnGoing() bool {
	return b.GetActionId() != action.InitialActionId && b.GetPhase() == v1.PhaseType_kOnGoing
}

// redo undo kfull 动作
func (b *drawPhase) IsCompleteDrawAction() bool {
	return b.GetPhase() == v1.PhaseType_kFull
}

// toProto
func (b *drawPhase) toProto(message proto.Message) (*v1.Action, error) {
	raw := b.emptyAction()
	data, err := proto.Marshal(message)
	if err != nil {
		return nil, biz_error.DecodeError.Wrap(errors.WithStack(err))
	}
	raw.Data = data
	return raw, nil
}

// drawDefault 全量替换的动作
type drawDefault struct {
	drawPhase
	proto.Message
}

func (d *drawDefault) ToProto() (*v1.Action, error) {
	return d.toProto(d.Message)
}

func (d *drawDefault) Execute(input object.Element) object.Element {
	if d.IsCreatingObject() {
		ele := object.CreateElementFromAction(d.AppId, d.RoomId, d.PageId, d.BoardId, d.Action, d.Message)
		if d.IsCompleteDrawAction() {
			ele.SetInitStatus(true)
		}
		return ele
	}

	// 默认情况下，使用全量替换
	input.Set(d.Message)
	input.AddActionCount(1)
	input.SetInitStatus(true)
	if d.GetPhase() == v1.PhaseType_kFinish || d.GetPhase() == v1.PhaseType_kFull {
		input.SetActionId(d.ActionId)
	}
	return input
}

type drawPen struct {
	drawPhase
	*v1.Pen
}

func (d *drawPen) ToProto() (*v1.Action, error) {
	return d.toProto(d.Pen)
}

// Execute start / ongoing / finish 应该是不同的 Phase 类
func (d *drawPen) Execute(input object.Element) object.Element {
	if d.IsCreatingObject() {
		ele := object.CreateElementFromAction(d.AppId, d.RoomId, d.PageId, d.BoardId, d.Action, d.Pen)
		if d.IsCompleteDrawAction() {
			ele.SetInitStatus(true)
		}
		return ele
	}
	input.AppendPoints(d)
	input.SetStyle(d)
	input.AddActionCount(1)
	input.SetInitStatus(true)
	if d.GetPhase() == v1.PhaseType_kFinish || d.GetPhase() == v1.PhaseType_kFull {
		input.SetActionId(d.ActionId)
	}
	return input
}
